/*
 * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI 
 * for visualizing and manipulating spatial features with geometry and attributes.
 *
 * JUMP is Copyright (C) 2003 Vivid Solutions
 *
 * This program implements extensions to JUMP and is
 * Copyright (C) 2007 Integrated Systems Analysts, Inc.
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * 
 * For more information, contact:
 *
 * Integrated Systems Analysts, Inc.
 * 630C Anchors St., Suite 101
 * Fort Walton Beach, Florida
 * USA
 *
 * (850)862-7321
 */package org.openjump.core.ui.plugin.layer;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import javax.swing.ImageIcon;
import javax.swing.JPopupMenu;

import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jump.I18N;
import com.vividsolutions.jump.feature.Feature;
import com.vividsolutions.jump.feature.FeatureCollection;
import com.vividsolutions.jump.feature.FeatureDataset;
import com.vividsolutions.jump.feature.FeatureSchema;
import com.vividsolutions.jump.workbench.WorkbenchContext;
import com.vividsolutions.jump.workbench.model.Layer;
import com.vividsolutions.jump.workbench.model.LayerManager;
import com.vividsolutions.jump.workbench.model.StandardCategoryNames;
import com.vividsolutions.jump.workbench.plugin.AbstractPlugIn;
import com.vividsolutions.jump.workbench.plugin.EnableCheckFactory;
import com.vividsolutions.jump.workbench.plugin.MultiEnableCheck;
import com.vividsolutions.jump.workbench.plugin.PlugInContext;
import com.vividsolutions.jump.workbench.ui.MenuNames;
import com.vividsolutions.jump.workbench.ui.images.IconLoader;
import com.vividsolutions.jump.workbench.ui.plugin.FeatureInstaller;

/**
 * This plugin split a layer into several layers when its features have
 * different geometry dimensions.
 * It can produce a maximum of 4 layers
 * <ul>
 * <li>Empty geometries</li>
 * <li>Punctual geometries</li>
 * <li>Linear geometries</li>
 * <li>Polygonal geometries</li>
 * </ul>
 * Rules followed to spread features into the 4 layers :
 * <ul>
 * <li>Empty GeometryCollection's are copied into empty-geometries layer</li>
 * <li>Non-empty GeometryCollections are exploded into simple geometry features</li>
 * <li>Empty simple features are copied into the layer corresponding to their dimension</li>
 * <li>Non-empty simple features are copied into the layer corresponding to their dimension</li>
 * </ul>
 */ 
public class ExtractLayersByGeometry extends AbstractPlugIn {

	   private final static String EXTRACT_LAYERS_BY_GEOMETRY_TYPE = 
	    	I18N.get("org.openjump.core.ui.plugin.layer.ExtractLayersByGeometry.Extract-Layers-by-Geometry-Type");
	   private final static String ONLY_ONE_GEOMETRY_TYPE_FOUND = 
	    	I18N.get("org.openjump.core.ui.plugin.layer.ExtractLayersByGeometry.Only-one-geometry-type-found");
	   private final static String EMPTY = 
	    	I18N.get("org.openjump.core.ui.plugin.layer.ExtractLayersByGeometry.empty");
	   private final static String POINT = 
	    	I18N.get("org.openjump.core.ui.plugin.layer.ExtractLayersByGeometry.point");
	   private final static String LINE = 
	    	I18N.get("org.openjump.core.ui.plugin.layer.ExtractLayersByGeometry.line");
	   private final static String AREA = 
	    	I18N.get("org.openjump.core.ui.plugin.layer.ExtractLayersByGeometry.area");
	 
	public ExtractLayersByGeometry() {

	}
	
	public void initialize(PlugInContext context) throws Exception {
		WorkbenchContext workbenchContext = context.getWorkbenchContext();
		FeatureInstaller featureInstaller = new FeatureInstaller(
				workbenchContext);
		/*
		JPopupMenu layerNamePopupMenu = workbenchContext.getWorkbench()
				.getFrame().getLayerNamePopupMenu();
		featureInstaller.addPopupMenuItem(layerNamePopupMenu, this, getName(),
				false, ICON, createEnableCheck(workbenchContext));
		*/
		//-- [sstein] this shouldn't be here, but as we try to use now the 
		//   default-plugins.xml for configuration, we need to add the submenu init
		//   in the first loaded submenu function
		featureInstaller.addMenuSeparator(MenuNames.EDIT);
        FeatureInstaller.addMainMenu(featureInstaller, new String[] {
                MenuNames.EDIT
              }, MenuNames.EXTRACT, 14);  
        //--
	    context.getFeatureInstaller().addMainMenuItemWithJava14Fix(this,
		        new String[]
				{MenuNames.EDIT, MenuNames.EXTRACT},
				getName(), 
				false, 
				ICON, 
				createEnableCheck(context.getWorkbenchContext()));
	}

    public static MultiEnableCheck createEnableCheck(WorkbenchContext workbenchContext) {
        EnableCheckFactory checkFactory = new EnableCheckFactory(workbenchContext);      
        return new MultiEnableCheck()
        .add(checkFactory.createWindowWithSelectionManagerMustBeActiveCheck())
        .add(checkFactory.createExactlyNLayersMustBeSelectedCheck(1));
    }  
    

	public boolean execute(PlugInContext context) throws Exception {
		Layer[] layers = context.getWorkbenchContext().getLayerNamePanel().getSelectedLayers();
		if (layers.length > 0){
			Layer layer = layers[0];
			if (!compatibleFeatures(layer)) 
				splitLayer(context, layer);
			else
				context.getWorkbenchFrame().warnUser(ONLY_ONE_GEOMETRY_TYPE_FOUND);
			return true;
		} else
			return false;
	}
	
	public String getName() {
		return EXTRACT_LAYERS_BY_GEOMETRY_TYPE;
	}

    public static final ImageIcon ICON = IconLoader.icon("extract.gif");


    private List splitLayer(PlugInContext context, Layer layer)
    {
        ArrayList newLayers = new ArrayList();
    	
        ArrayList emptyFeatures = new ArrayList();
        ArrayList pointFeatures = new ArrayList();
        ArrayList lineFeatures = new ArrayList();
        ArrayList polyFeatures = new ArrayList();
        
    	FeatureCollection featureCollection = layer.getFeatureCollectionWrapper();
        List featureList = featureCollection.getFeatures();
        FeatureSchema featureSchema = featureCollection.getFeatureSchema();

        Collection selectedCategories = context.getLayerNamePanel().getSelectedCategories();
        
        for (Iterator i = featureList.iterator(); i.hasNext();) {
            Feature feature = (Feature) i.next();
            Geometry geo = feature.getGeometry();
            BitSet currFeatureBit = new BitSet();
            currFeatureBit = setBit(currFeatureBit, geo);
            if (geo instanceof GeometryCollection) {
                explodeGeometryCollection(featureSchema, emptyFeatures, pointFeatures,
                        lineFeatures, polyFeatures, (GeometryCollection) geo, feature);
            } else if  (currFeatureBit.get(pointBit)) {
            		pointFeatures.add(feature.clone(true));
            } else if  (currFeatureBit.get(lineBit)) {
            		lineFeatures.add(feature.clone(true));
            } else if (currFeatureBit.get(polyBit)) {
            		polyFeatures.add(feature.clone(true));
            }
        }
        
        if (emptyFeatures.size() > 0) {
		    Layer emptyLayer = context.addLayer(selectedCategories.isEmpty()
		        ? StandardCategoryNames.RESULT
		        : selectedCategories.iterator().next().toString(), layer.getName() + "_" + EMPTY,
		        new FeatureDataset(featureSchema));
		    emptyLayer.setStyles(layer.cloneStyles());		        
		    FeatureCollection emptyFeatureCollection = emptyLayer.getFeatureCollectionWrapper();
		    newLayers.add(emptyLayer);	           
	        emptyFeatureCollection.addAll(emptyFeatures);
        }
        
        if (pointFeatures.size() > 0) {
		    Layer pointLayer = context.addLayer(selectedCategories.isEmpty()
		        ? StandardCategoryNames.RESULT
		        : selectedCategories.iterator().next().toString(), layer.getName() + "_" + POINT,
		        new FeatureDataset(featureSchema));
		    pointLayer.setStyles(layer.cloneStyles());		        
		    FeatureCollection pointFeatureCollection = pointLayer.getFeatureCollectionWrapper();
		    newLayers.add(pointLayer);	           
	        pointFeatureCollection.addAll(pointFeatures);
        }
        
        if (lineFeatures.size() > 0) {
 		    Layer lineLayer = context.addLayer(selectedCategories.isEmpty()
				? StandardCategoryNames.RESULT
				: selectedCategories.iterator().next().toString(), layer.getName() + "_" + LINE,
				new FeatureDataset(featureSchema));
			lineLayer.setStyles(layer.cloneStyles());				
		    FeatureCollection lineFeatureCollection = lineLayer.getFeatureCollectionWrapper();
		    newLayers.add(lineLayer);	           
			lineFeatureCollection.addAll(lineFeatures);
        }
        
        if (polyFeatures.size() > 0) {
 		    Layer polyLayer = context.addLayer(selectedCategories.isEmpty()
				? StandardCategoryNames.RESULT
				: selectedCategories.iterator().next().toString(), layer.getName() + "_" + AREA,
				new FeatureDataset(featureSchema));
		    polyLayer.setStyles(layer.cloneStyles());				        
		    FeatureCollection polyFeatureCollection = polyLayer.getFeatureCollectionWrapper();
		    newLayers.add(polyLayer);	           
		    polyFeatureCollection.addAll(polyFeatures);
        }
     	context.getLayerViewPanel().repaint();
    	return newLayers;
    }

	static final int emptyBit = 0;
	static final int pointBit = 1;
	static final int lineBit = 2;
	static final int polyBit = 3;
	
    private void explodeGeometryCollection(FeatureSchema fs,
                                           ArrayList emptyFeatures,
                                           ArrayList pointFeatures,
                                           ArrayList lineFeatures, 
                                           ArrayList polyFeatures,
                                           GeometryCollection geometryCollection,
                                           Feature feature) {
        
        if (geometryCollection.isEmpty()) {
            emptyFeatures.add(feature.clone(false));
        }
        else {
    	    for (int i = 0; i < geometryCollection.getNumGeometries(); i++) {
    		    Geometry geometry = geometryCollection.getGeometryN(i);
    		
    		    if (geometry instanceof GeometryCollection) {
    			    explodeGeometryCollection(fs, emptyFeatures, pointFeatures, lineFeatures,
    			            polyFeatures, (GeometryCollection)geometry, feature);
    		    }
    		    else {
    			    Feature newFeature = feature.clone(false);
    			    newFeature.setGeometry((Geometry) geometry.clone());
    			    BitSet featureBit = new BitSet();
    			    featureBit = setBit(featureBit, geometry);
    			    if (featureBit.get(pointBit)) {
    			    	pointFeatures.add(newFeature);
    			    } else if (featureBit.get(lineBit)) { 
    			    	lineFeatures.add(newFeature);
    			    } else if (featureBit.get(polyBit)) {
    			    	polyFeatures.add(newFeature);
    			    }
    		    }
    		}
    	}
    }

    private boolean compatibleFeatures(Layer layer) {
        BitSet bitSet = new BitSet();        
        FeatureCollection featureCollection = layer.getFeatureCollectionWrapper();
        List featureList = featureCollection.getFeatures();
        for (Iterator i = featureList.iterator(); i.hasNext();) {
            bitSet = setBit(bitSet, ((Feature) i.next()).getGeometry());
        }
        return (bitSet.cardinality() < 2);
    }
    

	private static BitSet setBit(BitSet bitSet, Geometry geometry) {
        BitSet newBitSet = (BitSet) bitSet.clone();
        if (geometry instanceof Point)                newBitSet.set(pointBit);
        else if (geometry instanceof MultiPoint)      newBitSet.set(pointBit);
        else if (geometry instanceof LineString)      newBitSet.set(lineBit);
        else if (geometry instanceof LinearRing)      newBitSet.set(lineBit);
        else if (geometry instanceof MultiLineString) newBitSet.set(lineBit);
        else if (geometry instanceof Polygon)         newBitSet.set(polyBit);
        else if (geometry instanceof MultiPolygon)    newBitSet.set(polyBit);
        else if (geometry instanceof GeometryCollection) {
            if (geometry.isEmpty())                   newBitSet.set(emptyBit);
            else {
                GeometryCollection geometryCollection = (GeometryCollection) geometry;
                for (int i = 0; i < geometryCollection.getNumGeometries(); i++) {
                    newBitSet = setBit(newBitSet, geometryCollection.getGeometryN(i));
                }
            }
        }
        return newBitSet;
    }
    


}
